package component

import (
	"context"

	"github.com/stackrox/rox/generated/internalapi/central"
	"github.com/stackrox/rox/generated/storage"
	"github.com/stackrox/rox/sensor/common/store/resolver"
)

// DeploytimeDetectionRequest is used to define a request for a detection based on a deployment object. This is message
// is processed by component.OutputQueue and sent to the detector component.
type DeploytimeDetectionRequest struct {
	// Object references the object that needs to be processed by the detector
	Object *storage.Deployment
	// Action of an event (CREATE_RESOURCE, REMOVE_RESOURCE, UPDATE_RESOURCE, and SYNC_RESOURCE)
	Action central.ResourceAction
}

// DeploymentReference is a message that encapsulates a resource's reference to an ACS deployment object.
// It has both the reference function (which fetches the deployment) but also the original event action that triggered
// this deployment reprocessing.
type DeploymentReference struct {
	// Reference is a callback function that can return a list of deployment ids that require processing.
	Reference resolver.DeploymentResolution

	// ParentResourceAction is the resource action that will be sent to central on the deployment event.
	// If the ResourceEvent originated on a deployment event, this should be set to whatever action triggered
	// the event. For related resources updates, like RBACs and services, this should always be set to
	// UPDATE.
	ParentResourceAction central.ResourceAction

	// ForceDetection is a flag that will force a detection even if the deployment has no changes.
	// This is needed to trigger detection of deployments associated with a NetworkPolicy, since they are not part of the deployment spec
	// and therefore will not be triggered since the deduper won't recognize that a deployment was changed.
	ForceDetection bool

	// SkipResolving indicates whether the deployments need to be resolved or not. This is set to true when a re-process
	// is triggered after receiving a message from central (e.g. UpdatedImage).
	SkipResolving bool
}

// ResourceEvent message used by the event pipeline's components
type ResourceEvent struct {
	// ForwardMessages messages generated by the handlers that need to be forwarded to central. These are usually resource
	// create, update or delete events.
	ForwardMessages []*central.SensorEvent
	// DetectorMessages hold the deployment objects that need to be processed against ACS policies.
	DetectorMessages []DeploytimeDetectionRequest
	// ReprocessDeployments resets the deployment ID in the detector deduper.
	ReprocessDeployments []string

	// DeploymentTiming has the timing object that needs to be added to any deployments resolved by this event.
	DeploymentTiming *central.Timing

	// DeploymentReferences returns a list of deployment references generated from a Kubernetes Event
	DeploymentReferences []DeploymentReference

	// Context contains a context that determines if the message is still valid.
	Context context.Context
}

// NewEvent creates a resource event with preset sensor event messages.
func NewEvent(msg ...*central.SensorEvent) *ResourceEvent {
	return &ResourceEvent{ForwardMessages: msg, Context: context.Background()}
}

// AddSensorEvent appends central sensor events to be bundled with this resource event.
func (e *ResourceEvent) AddSensorEvent(event ...*central.SensorEvent) *ResourceEvent {
	e.ForwardMessages = append(e.ForwardMessages, event...)
	return e
}

// AddDeploymentForDetection appends deployment detection messages. Check DeploytimeDetectionRequest docs.
func (e *ResourceEvent) AddDeploymentForDetection(messages ...DeploytimeDetectionRequest) *ResourceEvent {
	e.DetectorMessages = append(e.DetectorMessages, messages...)
	return e
}

// AddDeploymentForReprocessing appends one or more deployment ids that require reprocessing. Check ReprocessDeployments docs.
func (e *ResourceEvent) AddDeploymentForReprocessing(ids ...string) *ResourceEvent {
	e.ReprocessDeployments = append(e.ReprocessDeployments, ids...)
	return e
}

// DeploymentReferenceOption option function for the DeploymentReference struct
type DeploymentReferenceOption func(*DeploymentReference)

// WithParentResourceAction sets the ParentResourceAction
func WithParentResourceAction(action central.ResourceAction) DeploymentReferenceOption {
	return func(dr *DeploymentReference) {
		dr.ParentResourceAction = action
	}
}

// WithForceDetection sets whether the detection should be forced or not
func WithForceDetection() DeploymentReferenceOption {
	return func(dr *DeploymentReference) {
		dr.ForceDetection = true
	}
}

// WithSkipResolving sets whether the deployment should be resolved or not
func WithSkipResolving() DeploymentReferenceOption {
	return func(dr *DeploymentReference) {
		dr.SkipResolving = true
	}
}

// newDeploymentReference creates a new deployment reference and applies all the DeploymentReferenceOptions
func newDeploymentReference(reference resolver.DeploymentResolution, options ...DeploymentReferenceOption) DeploymentReference {
	deploymentReference := DeploymentReference{
		Reference:            reference,
		ParentResourceAction: central.ResourceAction_UPDATE_RESOURCE,
		ForceDetection:       false,
		SkipResolving:        false,
	}
	for _, opt := range options {
		opt(&deploymentReference)
	}
	return deploymentReference
}

// AddDeploymentReference creates and sets a new deployment reference to this resource event.
func (e *ResourceEvent) AddDeploymentReference(reference resolver.DeploymentResolution, options ...DeploymentReferenceOption) {
	e.DeploymentReferences = append(e.DeploymentReferences, newDeploymentReference(reference, options...))
}

// MergeResourceEvent appends properties from `ev` into resource event message, without updating resource timing.
func (e *ResourceEvent) MergeResourceEvent(ev *ResourceEvent) {
	if ev == nil {
		return
	}
	e.ReprocessDeployments = append(e.ReprocessDeployments, ev.ReprocessDeployments...)
	e.ForwardMessages = append(e.ForwardMessages, ev.ForwardMessages...)
	e.DetectorMessages = append(e.DetectorMessages, ev.DetectorMessages...)
	e.DeploymentReferences = append(e.DeploymentReferences, ev.DeploymentReferences...)
}
